function uid() {
  return window.crypto.getRandomValues(new Uint32Array(1))[0];
}

function transformCallback(callback, once = false) {
  const identifier = uid();
  const prop = `_${identifier}`;

  Object.defineProperty(window, prop, {
    value: (result) => {
      if (once) {
        Reflect.deleteProperty(window, prop);
      }

      return callback?.(result);
    },
    writable: false,
    configurable: true,
  });

  return identifier;
}

async function invoke(cmd, args, callback) {
  return new Promise((reslove, reject) => {
    const { port1, port2 } = new MessageChannel();
    port1.onmessage = ({ data, ...rest }) => {
      callback?.(JSON.parse(data));
      if (data.error) {
        reject(data);
      } else {
        reslove(data);
      }
    };
    window.parent.postMessage(
      JSON.stringify({
        __func__: 'tauri.invoke',
        args: [cmd, args],
      }),
      '*',
      [port2],
    );
  });
}

async function invokeTauriCommand(command) {
  return invoke('tauri', command);
}

async function execute(onEvent, program, args = [], options) {
  if (typeof args === 'object') {
    Object.freeze(args);
  }
  const res = await invoke(
    'tauri',
    {
      __tauriModule: 'Shell',
      message: {
        cmd: 'execute',
        program,
        args,
        options,
        // onEventFn: transformCallback(onEvent),
      },
    },
    onEvent,
  );
  console.log('iframe-execute:', res);
}
class EventEmitter {
  constructor() {
    this.eventListeners = /* @__PURE__ */ Object.create(null);
  }
  addEventListener(event, handler) {
    if (event in this.eventListeners) {
      this.eventListeners[event].push(handler);
    } else {
      this.eventListeners[event] = [handler];
    }
  }
  _emit(event, payload) {
    if (event in this.eventListeners) {
      const listeners = this.eventListeners[event];
      for (const listener of listeners) {
        listener(payload);
      }
    }
  }
  on(event, handler) {
    this.addEventListener(event, handler);
    return this;
  }
}
class Child {
  constructor(pid) {
    this.pid = pid;
  }
  async write(data) {
    return invokeTauriCommand({
      __tauriModule: 'Shell',
      message: {
        cmd: 'stdinWrite',
        pid: this.pid,
        buffer: typeof data === 'string' ? data : Array.from(data),
      },
    });
  }
  async kill() {
    return invokeTauriCommand({
      __tauriModule: 'Shell',
      message: {
        cmd: 'killChild',
        pid: this.pid,
      },
    });
  }
}
class Command extends EventEmitter {
  constructor(program, args = [], options) {
    super();
    this.stdout = new EventEmitter();
    this.stderr = new EventEmitter();
    this.program = program;
    this.args = typeof args === 'string' ? [args] : args;
    this.options = options != null ? options : {};
  }
  static sidecar(program, args = [], options) {
    const instance = new Command(program, args, options);
    instance.options.sidecar = true;
    return instance;
  }
  async spawn() {
    return execute(
      (event) => {
        switch (event.event) {
          case 'Error':
            this._emit('error', event.payload);
            break;
          case 'Terminated':
            this._emit('close', event.payload);
            break;
          case 'Stdout':
            this.stdout._emit('data', event.payload);
            break;
          case 'Stderr':
            this.stderr._emit('data', event.payload);
            break;
        }
      },
      this.program,
      this.args,
      this.options,
    ).then((pid) => new Child(pid));
  }
  async execute() {
    return new Promise((resolve, reject) => {
      this.on('error', reject);
      const stdout = [];
      const stderr = [];
      this.stdout.on('data', (line) => {
        stdout.push(line);
      });
      this.stderr.on('data', (line) => {
        stderr.push(line);
      });
      this.on('close', (payload) => {
        resolve({
          code: payload.code,
          signal: payload.signal,
          stdout: stdout.join('\n'),
          stderr: stderr.join('\n'),
        });
      });
      this.spawn().catch(reject);
    });
  }
  // async execute() {
  //   return new Promise((reslove, reject) => {
  //     const { port1, port2 } = new MessageChannel();
  //     port1.onmessage = ({ data }) => {
  //       if (data.error) {
  //         reject(data);
  //       } else {
  //         reslove(data);
  //       }
  //     };
  //     window.parent.postMessage(
  //       JSON.stringify({
  //         __func__: 'shell.Command',
  //         args: [this.program, this.args],
  //       }),
  //       '*',
  //       [port2],
  //     );
  //   });
  // }
}
async function open(path, openWith) {
  return invokeTauriCommand({
    __tauriModule: 'Shell',
    message: {
      cmd: 'open',
      path,
      with: openWith,
    },
  });
}

export const tauri = {
  invoke,
};

export const shell = {
  Child,
  Command,
  EventEmitter,
  open,
};

window._ = { shell, tauri };

export default window._;
